package top.cardone.data.jdbc.dao.impl;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.util.CollectionUtils;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.util.func.Func1;
import top.cardone.data.jdbc.support.NamedParameterJdbcOperationsSupport;

import java.util.List;
import java.util.Map;

/**
 * Created by yao hai tao on 2015/8/26.
 */
@Log4j2
public class CrudDaoImpl implements top.cardone.data.dao.CrudDao {
    @lombok.Setter
    protected Table<String, String, String> configTable = HashBasedTable.create();

    @Override
    public int delete(Object delete) {
        String sqlFilePath = this.getSqlFilePath("delete");

        Map<String, Object> deleteMap = this.toMap(delete, "delete");

        Map<String, Object> newDeleteMap = Maps.newHashMap();

        for (Map.Entry<String, Object> deleteEntry : deleteMap.entrySet()) {
            newDeleteMap.put("where_and_eq_" + deleteEntry.getKey(), Boolean.TRUE.toString());
            newDeleteMap.put("where_and_eq_" + deleteEntry.getKey() + "_value", deleteEntry.getValue());
        }

        return this.update(sqlFilePath, newDeleteMap);
    }

    @Override
    public int deleteAll() {
        String sqlFilePath = this.getSqlFilePath("delete");

        Map<String, Object> newDeleteMap = Maps.newHashMap();

        newDeleteMap.put("delete_all", Boolean.TRUE.toString());

        return this.update(sqlFilePath, newDeleteMap);
    }

    @Override
    public int deleteByIds(Object ids) {
        String sqlFilePath = this.getSqlFilePath("deleteByIds");

        return this.update(sqlFilePath, this.toMap(ids, "ids"));
    }

    @Override
    public int[] deleteList(List<Object> deleteList) {
        if (CollectionUtils.isEmpty(deleteList)) {
            return new int[0];
        }

        String sqlFilePath = this.getSqlFilePath("delete");

        List<Map<String, Object>> newDeleteListMap = Lists.newArrayList();

        for (Object delete : deleteList) {
            Map<String, Object> deleteMap = this.toMap(delete, "delete");

            Map<String, Object> newDeleteMap = Maps.newHashMap();

            for (Map.Entry<String, Object> deleteEntry : deleteMap.entrySet()) {
                newDeleteMap.put("where_and_eq_" + deleteEntry.getKey(), Boolean.TRUE.toString());
                newDeleteMap.put("where_and_eq_" + deleteEntry.getKey() + "_value", deleteEntry.getValue());
            }

            newDeleteListMap.add(newDeleteMap);
        }

        return this.updateList(sqlFilePath, newDeleteListMap);
    }

    @Override
    public List<Map<String, Object>> findList(Object findList) {
        String sqlFilePath = this.getSqlFilePath("find");

        Map<String, Object> findListMap = this.toMap(findList, "find");

        Map<String, Object> newFindListMap = Maps.newHashMap();

        for (Map.Entry<String, Object> findListEntry : findListMap.entrySet()) {
            newFindListMap.put("where_and_eq_" + findListEntry.getKey(), Boolean.TRUE.toString());
            newFindListMap.put("where_and_eq_" + findListEntry.getKey() + "_value", findListEntry.getValue());
        }

        return this.findList(sqlFilePath, newFindListMap);
    }

    protected List<Map<String, Object>> findList(String sqlFilePath) {
        return this.findList(sqlFilePath, null);
    }

    protected List<Map<String, Object>> findList(String sqlFilePath, Map<String, ?> findList) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, findList, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.queryForList(sql, findList));
    }

    @Override
    public Map<String, Object> findOne(Object findOne) {
        String sqlFilePath = this.getSqlFilePath("find");

        Map<String, Object> findOneMap = this.toMap(findOne, "find");

        Map<String, Object> newFindOneMap = Maps.newHashMap();

        for (Map.Entry<String, Object> findOneEntry : findOneMap.entrySet()) {
            newFindOneMap.put("where_and_eq_" + findOneEntry.getKey(), Boolean.TRUE.toString());
            newFindOneMap.put("where_and_eq_" + findOneEntry.getKey() + "_value", findOneEntry.getValue());
        }

        return this.findOne(sqlFilePath, newFindOneMap);
    }

    protected Map<String, Object> findOne(String sqlFilePath, Map<String, ?> findOne) {
        List<Map<String, Object>> findList = this.findList(sqlFilePath, findOne);

        if (CollectionUtils.isEmpty(findList)) {
            return null;
        }

        if (findList.size() > 1) {
            log.error("单条数据查询方法，查询出" + findList.size() + "条数据:sqlFilePath=" + sqlFilePath + ", findOne=" + findOne);
        }

        return findList.get(0);
    }

    protected String[] getCodes() {
        return StringUtils.split(StringUtils.remove(configTable.get("config", "codes"), " "), ",");
    }

    protected String getNamedParameterJdbcOperationsBeanId() {
        return configTable.get("config", "namedParameterJdbcOperationsBeanId");
    }

    protected String getParseBeanId() {
        return configTable.get("config", "parseBeanId");
    }

    protected String[] getPks() {
        String[] pks = StringUtils.split(StringUtils.remove(configTable.get("config", "pks"), " "), ",");

        if (ArrayUtils.isNotEmpty(pks)) {
            return pks;
        }

        return this.getCodes();
    }

    protected String[] getWheres(Map<String, Object> objectMap) {
        String[] pks = this.getPks();

        String[] codes = this.getCodes();

        if (ArrayUtils.isEmpty(codes)) {
            return pks;
        }

        for (String pk : pks) {
            Object val = objectMap.get(pk);

            if (val == null || (val instanceof String && StringUtils.isBlank(val.toString()))) {
                return codes;
            }
        }

        return pks;
    }

    protected String getSqlFilePath(String sqlFilePath) {
        if (StringUtils.startsWith(sqlFilePath, "/")) {
            return sqlFilePath;
        }

        String sqlFileRoot = configTable.get("config", "sqlFileRoot");

        String sqlFileSuffix = StringUtils.defaultIfBlank(configTable.get("config", "sqlFileSuffix"), ".ftl");

        return sqlFileRoot + sqlFilePath + sqlFileSuffix;
    }

    @Override
    public int insert(Object insert) {
        String sqlFilePath = this.getSqlFilePath("insert");

        Map<String, Object> insertMap = this.toMap(insert, "insert");

        Map<String, Object> newInsertMap = Maps.newHashMap();

        for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
            newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
            newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
        }

        return this.update(sqlFilePath, newInsertMap);
    }

    @Override
    public int insertByNotExists(Object insert) {
        String sqlFilePath = this.getSqlFilePath("insertByNotExists");

        Map<String, Object> insertMap = this.toMap(insert, "insert");

        Map<String, Object> newInsertMap = Maps.newHashMap();

        String[] codes = this.getCodes();

        for (String code : codes) {
            newInsertMap.put("where_and_eq_" + code, Boolean.TRUE.toString());
            newInsertMap.put("where_and_eq_" + code + "_value", insertMap.get(code));
        }

        for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
            newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
            newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
        }

        return this.update(sqlFilePath, newInsertMap);
    }

    @Override
    public int[] insertList(List<Object> insertList) {
        if (CollectionUtils.isEmpty(insertList)) {
            return new int[0];
        }

        String sqlFilePath = this.getSqlFilePath("insert");

        List<Map<String, Object>> newInsertListMap = Lists.newArrayList();

        for (Object insert : insertList) {
            Map<String, Object> insertMap = this.toMap(insert, "insert");

            Map<String, Object> newInsertMap = Maps.newHashMap();

            for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
                newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
                newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
            }

            newInsertListMap.add(newInsertMap);
        }

        return this.updateList(sqlFilePath, newInsertListMap);
    }

    @Override
    public int[] insertListByNotExists(List<Object> insertList) {
        if (CollectionUtils.isEmpty(insertList)) {
            return new int[0];
        }

        String sqlFilePath = this.getSqlFilePath("insertByNotExists");

        List<Map<String, Object>> newInsertListMap = Lists.newArrayList();

        String[] codes = this.getCodes();

        for (Object insert : insertList) {
            Map<String, Object> insertMap = this.toMap(insert, "insert");

            Map<String, Object> newInsertMap = Maps.newHashMap();

            for (String code : codes) {
                newInsertMap.put("where_and_eq_" + code, Boolean.TRUE.toString());
                newInsertMap.put("where_and_eq_" + code + "_value", insertMap.get(code));
            }

            for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
                newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
                newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
            }

            newInsertListMap.add(newInsertMap);
        }

        return this.updateList(sqlFilePath, newInsertListMap);
    }

    @Override
    public List<Object> readList(Object readList) {
        String sqlFilePath = this.getSqlFilePath("read");

        Map<String, Object> readListMap = this.toMap(readList, "read");

        Map<String, Object> newReadListMap = Maps.newHashMap();

        for (Map.Entry<String, Object> readListEntry : readListMap.entrySet()) {
            newReadListMap.put("where_and_eq_" + readListEntry.getKey(), Boolean.TRUE.toString());
            newReadListMap.put("where_and_eq_" + readListEntry.getKey() + "_value", readListEntry.getValue());
        }

        newReadListMap.put("object_id", readListMap.get("object_id"));

        return this.readList(sqlFilePath, newReadListMap);
    }

    protected List<Object> readList(String sqlFilePath, Map<String, ?> readList) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, readList, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.queryForList(sql, readList, Object.class));
    }

    @Override
    public Object readOne(Object readOne) {
        String sqlFilePath = this.getSqlFilePath("read");

        Map<String, Object> readOneMap = this.toMap(readOne, "read");

        Map<String, Object> newReadOneMap = Maps.newHashMap();

        for (Map.Entry<String, Object> readOneEntry : readOneMap.entrySet()) {
            if ("object_id".equals(readOneEntry.getKey())) {
                continue;
            }

            newReadOneMap.put("where_and_eq_" + readOneEntry.getKey(), Boolean.TRUE.toString());
            newReadOneMap.put("where_and_eq_" + readOneEntry.getKey() + "_value", readOneEntry.getValue());
        }

        newReadOneMap.put("object_id", readOneMap.get("object_id"));

        return this.readOne(sqlFilePath, newReadOneMap);
    }

    protected Object readOne(String sqlFilePath, Map<String, ?> readOne) {
        List<Object> readList = this.readList(sqlFilePath, readOne);

        if (CollectionUtils.isEmpty(readList)) {
            return null;
        }

        if (readList.size() > 1) {
            log.error("单条数据查询方法，查询出" + readList.size() + "条数据:sqlFilePath=" + sqlFilePath + ", readOne=" + readOne);
        }

        return readList.get(0);
    }

    @Override
    public Integer save(Object save) {
        return this.save(null, null, save);
    }

    protected Integer save(String updateSqlFilePath, String insertSqlFilePath, Object save) {
        int updateCount;

        if (StringUtils.isBlank(updateSqlFilePath)) {
            updateCount = this.update(save);
        } else {
            Map<String, Object> updateMap = this.toMap(save, "update");

            updateCount = this.update(updateSqlFilePath, updateMap);
        }

        if (updateCount > 0) {
            return updateCount;
        }

        if (StringUtils.isBlank(insertSqlFilePath)) {
            return this.insertByNotExists(save);
        } else {
            Map<String, Object> insertMap = this.toMap(save, "insert");

            return this.update(insertSqlFilePath, insertMap);
        }
    }

    public void setConfigTable(Map<String, Map<String, String>> table) {
        if (MapUtils.isEmpty(table)) {
            return;
        }

        for (Map.Entry<String, Map<String, String>> tableEntry : table.entrySet()) {
            for (Map.Entry<String, String> rowEntry : tableEntry.getValue().entrySet()) {
                configTable.put(tableEntry.getKey(), rowEntry.getKey(), rowEntry.getValue());
            }
        }
    }

    protected Map<String, Object> toMap(Object obj, String key) {
        Map<String, Object> newMap = top.cardone.context.util.MapUtils.toMap(obj, key);

        Map<String, String> row = configTable.row(StringUtils.defaultString(key, "update"));

        if (MapUtils.isEmpty(row)) {
            return newMap;
        }

        for (Map.Entry<String, String> rowEntry : row.entrySet()) {
            Object value = newMap.get(rowEntry.getKey());

            if (value == null || ((value instanceof String) && StringUtils.isBlank((String) value))) {
                newMap.put(rowEntry.getKey(), ApplicationContextHolder.func(Func1.class, func -> func.func(newMap), rowEntry.getValue()));
            }
        }

        return newMap;
    }

    @Override
    public int update(Object update) {
        String sqlFilePath = this.getSqlFilePath("update");

        Map<String, Object> updateMap = this.toMap(update, "update");

        Map<String, Object> newUpdateMap = Maps.newHashMap();

        String[] wheres = this.getWheres(updateMap);

        for (String where : wheres) {
            newUpdateMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
            newUpdateMap.put("where_and_eq_" + where + "_value", updateMap.get(where));
        }

        for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
            if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
                continue;
            }

            newUpdateMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
            newUpdateMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
        }

        return this.update(sqlFilePath, newUpdateMap);
    }

    protected int update(String sqlFilePath, Map<String, ?> update) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, update, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.update(sql, update));
    }

    @Override
    public int[] updateList(List<Object> updateList) {
        if (CollectionUtils.isEmpty(updateList)) {
            return new int[0];
        }

        String sqlFilePath = this.getSqlFilePath("update");

        List<Map<String, Object>> newUpdateListMap = Lists.newArrayList();

        for (Object update : updateList) {
            Map<String, Object> updateMap = this.toMap(update, "update");

            Map<String, Object> newUpdateMap = Maps.newHashMap();

            String[] wheres = this.getWheres(updateMap);

            for (String where : wheres) {
                newUpdateMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
                newUpdateMap.put("where_and_eq_" + where + "_value", updateMap.get(where));
            }

            for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
                if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
                    continue;
                }

                newUpdateMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
                newUpdateMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
            }

            newUpdateListMap.add(newUpdateMap);
        }

        return this.updateList(sqlFilePath, newUpdateListMap);
    }

    @Override
    public int[][] saveList(List<Object> saveList) {
        int[] updateListCount = this.updateList(saveList);

        List<Object> insertList = Lists.newArrayList();

        for (int i = 0; i < updateListCount.length; i++) {
            if (updateListCount[i] < 1) {
                insertList.add(saveList.get(i));
            }
        }

        int[] insertListCount = this.insertListByNotExists(insertList);

        return new int[][]{updateListCount, insertListCount};
    }

    protected int[] updateList(String sqlFilePath, List<Map<String, Object>> updateList) {
        int[] updateCounts = new int[updateList.size()];

        for (int i = 0; i < updateList.size(); i++) {
            updateCounts[i] = this.update(sqlFilePath, updateList.get(i));
        }

        return updateCounts;
    }

    protected int[] updateListForStaticSql(String sqlFilePath, List<Map<String, Object>> updateList) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        final SqlParameterSource[] batchArgs = new SqlParameterSource[updateList.size()];

        for (int i = 0; i < updateList.size(); i++) {
            batchArgs[i] = new MapSqlParameterSource(updateList.get(i));
        }

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, updateList.get(0), namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.batchUpdate(sql, batchArgs));
    }
}